﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.ComponentModel;

namespace CountJobTags{
    /// <summary>The available choices for Y-Axis on the graphing</summary>
    public enum YValueField { 
        /// <summary>Display how many jobs have the tag, after filtering</summary>
        Count, 
        /// <summary>Age of the ad in hours</summary>
        AvgHours, 
        /// <summary>What portion of the jobs for this tag allow remote work</summary>
        PctAllowingRemote, 
        /// <summary>For every tag with this tag, how many other skills are demanded by employers?</summary>
        AvgOtherSkillCount };

    /// <summary>
    /// An object useful for analyzing a job tag
    /// </summary>
    /// <remarks>
    /// Anticipated usage is that caller will supply a filter Predicate function that can be used
    /// for revising the counts/avg job open length.
    /// </remarks>
    public class TagAndCount : INotifyPropertyChanged, ICloneable {
        #region Properties
        /// <summary>Such as "C#"</summary>
        public string Tag { get; set; }

        /// <summary>The most common tag has rank 1</summary>
        public int Rank { get; set; }

        /// <summary>What percentage of all listed jobs want this skill</summary>
        public double Pct { get; set; }

        /// <summary>A value used when graphing that can vary according to the which field has been selected for Y-Value</summary>
        public string DisplayNumber { get; set; }

        /// <summary>What color to use for the bar chart</summary>
        private Brush _BrushColor;
        public Brush BrushColor {
            get { return _BrushColor; }
            set { 
                _BrushColor = value;
                if (_BrushColor.CanFreeze) {
                    _BrushColor.Freeze();
                }
            }
        }
        
        /// <summary>For this skill, lists all the skills and the number of times they are asked for by employers</summary>
        public Dictionary<string, int> RelatedTagCounts { get; set; }

        private int _Count;
        /// <summary>How many times the skill has been listed, after filtering by users' selection criteria</summary>
        public int Count {
            get {
                if (_Count == int.MinValue) {
                    _Count = _JobList.Count(j => _Filter(j));
                }

                return _Count; 
            }
            set { _Count = value; }
        }

        private List<JobDesc> _JobList;
        /// <summary>List of all the jobs that ask for this skill</summary>
        public List<JobDesc> JobList {
            get { return _JobList; }
            set { 
                _JobList = value;
                if (Tag == "mule")
                    Console.WriteLine("mule");
                _AvgAgeInHours = JobList.Average(tg => tg.Age.TotalHours);
            }
        }

        private double _AvgAgeInHours;
        /// <summary>How long the a job requireing this skill has been posted, in hours</summary>
        public double AvgAgeInHours {
            //if the Average has already been computed, return it, but otherwise calculate it now
            get {
                if (_AvgAgeInHours == double.MinValue) {
                    if (JobList == null) {
                        return 0.0;
                    } else {
                        var filtered = JobList.Where(j => _Filter(j));
                        if (filtered.Count() == 0) {
                            _AvgAgeInHours = 0.0;
                        } else {
                            _AvgAgeInHours = JobList.Where(j => _Filter(j)).Average(tg => tg.Age.TotalHours);
                        }
                    }
                }
                return _AvgAgeInHours;
            }
            set { 
                _AvgAgeInHours = value; 
            }
        }

        /// <summary>Which value to display when graphing</summary>
        public double YValue { get; set; }

        private double _PctAllowingRemote;
        /// <summary>Percentage of jobs for this skill that allow remote work</summary>
        public double PctAllowingRemote {
            get {
                if (_PctAllowingRemote == double.MinValue) {
                    if (_JobList == null) {
                        return 0.0;
                    } else {
                        _PctAllowingRemote = ((double)_JobList.Count(j => j.AllowsRemote)) / _JobList.Count;
                    }
                }
                return _PctAllowingRemote;
            }
        }

        private Func<JobDesc, bool> _Filter;
        /// <summary>
        /// Used to determine which jobs are eligible for counting and averaging
        /// </summary>
        public Func<JobDesc, bool> Filter {
            get {
                return _Filter;
            }
            set {
                _Filter = value;
                //force these to be re-computed the next time they are requested
                _Count = int.MinValue;
                _AvgAgeInHours = double.MinValue;
                _PctAllowingRemote = double.MinValue;
            }
        }

        /// <summary>Average number of other skills listed when this skill is required</summary>
        public double AvgNumRelatedSkills { get; set; }

        /// <summary>Gets a list of related jobs with counts and percentages, orderd with most common on top</summary>
        public IEnumerable<RelatedJob> FormattedRelatedJobs {
            get {
                List<RelatedJob> result = RelatedTagCounts
                                            .OrderByDescending(r => r.Value)
                                            .Select(r => new RelatedJob { RelatedSkill = r.Key, Pct = ((double)r.Value) / Count, Count = r.Value })
                                            .ToList<RelatedJob>();
                return result;
            }
        }

        /// <summary>Whether the item is selected when displayed in a grid</summary>
        private bool _IsSelected;

        public bool IsSelected {
            get { return _IsSelected; }
            set { 
                _IsSelected = value;
                if (PropertyChanged != null) {
                    PropertyChanged(this, new PropertyChangedEventArgs("IsSelected"));
                }
            }
        }

        /// <summary>
        /// A number between 0.0 and 1.0 indicating the ranking of NumberAvgJob for this job relative to the highest
        /// </summary>
        public double OtherJobAvgPct { get; set; }

        /// <summary>
        /// A number between 0 and 1 indicating the ranking of this job compared to the oldest
        /// </summary>
        public double AvgJobAgePct { get; set; }
        #endregion

        #region Constructors
        /// <summary>Default constructor</summary>
        public TagAndCount() {
            _AvgAgeInHours = double.MinValue;
            _PctAllowingRemote = double.MinValue;
            _Count = int.MinValue;
            _Filter = c => true;
            RelatedTagCounts = new Dictionary<string, int>();
        }
        #endregion

        #region Methods
        /// <summary>
        /// Used when you want to modify which value is displayed on the chart
        /// </summary>
        /// <param name="universeCount"></param>
        /// <param name="fieldToUse"></param>
        /// <param name="maxScale"></param>
        public void SetYValue(double universeCount, YValueField fieldToUse, int maxScale) {
            if (fieldToUse == YValueField.Count) {
                YValue = maxScale * ((double)Count) / universeCount;
                DisplayNumber = Count.ToString();
            } else if (fieldToUse == YValueField.AvgHours) {
                YValue = maxScale * AvgAgeInHours / universeCount;
                DisplayNumber = AvgAgeInHours.ToString("N0");
            } else if (fieldToUse == YValueField.PctAllowingRemote) {
                YValue = maxScale * PctAllowingRemote;
                DisplayNumber = PctAllowingRemote.ToString("P");
            } else {
                YValue = maxScale * AvgNumRelatedSkills / universeCount;
                DisplayNumber = AvgNumRelatedSkills.ToString("N2");
            }
        }
        #endregion

        public event PropertyChangedEventHandler PropertyChanged;

        public object Clone() {
            return this.MemberwiseClone();
        }
    }

    /// <summary>
    /// Useful for sorting by Tag, also for binary search on tag
    /// </summary>
    public class TagAndCountIDComparer : IComparer<TagAndCount> {
        /// <summary>
        /// Does the comparison, allowing alphabetical sort on tag
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public int Compare(TagAndCount x, TagAndCount y) {
            return string.Compare(x.Tag, y.Tag);
        }
    }

    /// <summary>
    /// Suitable for sorting descending by count, then by tag
    /// </summary>
    public class TagAndCountDefaultSortComparer : IComparer<TagAndCount> {
        /// <summary>
        /// Does the comparison, allowing sort by tag, then by count
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        public int Compare(TagAndCount x, TagAndCount y) {
            int result = y.Count - x.Count;
            if (result == 0) {
                result = string.Compare(x.Tag, y.Tag);
            }
            return result;
        }
    }
}
